iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0
自我挑戰組

從「通靈系統」到穩健架構的契機系列 第 2

[Day 2] 魔法字串的代價比你想的高

  • 分享至 

  • xImage
  •  

先從入職印象最深刻的程式碼開始

當時主管跟我說:「這種類型的欄位會常改,所以取資料用DataTable,不要用 Model Binding」
我:「好,我會維持既有程式碼風格」
但我心裡卻在想——不管怎樣,最後這個魔法字串還不是要改?
/images/emoticon/emoticon10.gif

DataTable dt = _someQuery.GetInfo(x);

// 檢查限制條件 狀態要是WAIT 才可以進行變更流程 
if (dt.Rows[0]["STATUS_CODE"].ToString().ToUpper().Trim() != "WAITING")
{
    throw new Exception("STATUS_CODE SHOULD BE WAITING");
}

https://ithelp.ithome.com.tw/upload/images/20250815/201781620obY3G25TN.jpg

放上一張同源問題的梗圖,以上只是冰山一角,那時的專案特徵:

  • ❌ 沒有 Model Binding(全靠 DataTable 靈魂對話)

  • ❌ 欄位命名模糊到需要「靈感」或「通靈」才能猜出用途


吐槽前先說句公道話 — DataTable 的優點

  1. 免定義 Model → 想查什麼欄位直接塞進去

  2. 動態欄位支援 → 適合欄位不固定的資料(CSV / Excel)

  3. 跟舊系統相容性好 → 很多老 API、報表元件只吃 DataTable

  4. 一次裝多筆資料 → 記憶體中快速過濾、分組

不過放到 2025 年的新專案,它的優點很容易變成技術債,因為會:

  • 依賴魔法字串

  • 缺乏型別安全

  • 效能輸給 Model ORM(像 Dapper)


為什麼這樣寫會是一種成本?(錯誤 & 修正對照版)


1. 魔法字串邪教

錯誤示例:

if (dt.Rows[0]["STATUS_CODE"].ToString() == "WAITING") { ... }

"WAITING""STATUS_CODE" 全是 Hard Coding,散落四海八荒,改一次要全專案搜尋,還要祈禱大小寫、空白沒暗殺你。

你不加 Enum,我只能去擲筊問神佛,才知道你在定義什麼。

修正版:

public enum StatusCode
{
    [Display(Name = "等待", Description = "產線運送中")]
    Waiting,
    
    [Display(Name = "作業中", Description = "清洗中")]
    Processing,
    
    [Display(Name = "完成", Description = "已完成清洗")]
    Completed
}

if (order.StatusCode == StatusCode.Waiting) { ... }

型別安全、你IDE可以輔助你、改名不用全專案在那邊搜。


2. IndexOutOfRange 驚喜包

錯誤示例:

if (dt.Rows[0]["STATUS_CODE"].ToString() != "WAITING") { ... }

dt資料為空?恭喜觸發 IndexOutOfRangeException,例外訊息簡潔到令人感動(反諷),前端只能回:

「欸後端,爆了,你自己看…我只是按了查詢而已。」

修正版:

if (!dt.Any())
    throw new NotFoundException("Order not found");

真要這樣寫,也要先檢查資料是否存在,並用有意義的例外類別,讓前端知道是「資料不存在」而不是「系統壞了」。


3. DataTable 無型別大樂透

錯誤示例:

(int)row["ORDER_QTY"]

某個同事可能把欄位型別改成 decimal,上線直接 InvalidCastException,沒有任何編譯期警告。

修正版:

public class Order
{
    public decimal OrderQty { get; set; }
}

var order = conn.QueryFirstOrDefault<Order>(...);

用 Model + Dapper,欄位型別錯誤直接在編譯期爆。


4. 測試?什麼測試?這種抽象的Code能寫測試?

錯誤示例:

public void UpdateOrder(string id)
{
    // 原始程式碼有500行吧,有部分邏輯甚至寫在預存程式,給我整無語
    // 查詢 + 驗證 + 寫入 + 發通知 全黏在一起
}

要測試某段邏輯得 mock 半個世界,還要假裝有 DB 和 SMTP Server,我寫測試比寫邏輯還麻煩是怎= =

修正版:

public bool CanUpdateOrder(Order order) { ... } // 純邏輯,可單測
public void UpdateOrderInDb(Order order) { ... }
public void NotifyOrderUpdated(Order order) { ... }

拆成可單獨測試的邏輯方法,減少外部依賴。


5. Exception 亂丟風格

錯誤示例:

throw new Exception("STATUS_CODE SHOULD BE WAITING");

前端完全不知道這是查無資料、狀態錯誤,還是 DBA 在搞破壞。

修正版:

throw new BusinessRuleException(
    "Order status must be WAITING",
    ErrorCodes.StatusNotWaiting
);

自訂例外類型 + 錯誤碼,前端可依錯誤碼判斷行為並顯示友善訊息。


總結

DataTable 不是原罪,在舊系統或特定場景它依然好用,但新專案長期維護時,缺點會被無限放大。

能動 ≠ 寫得好。
在多人協作的專案裡,型別安全、可測試、可維護,比事後救火更省命。


上一篇
[Day 1] 參加原因 & 程式能動就好,幹嘛要改?
下一篇
[Day 3] 資料表結構混亂:一張表看出專案的未來
系列文
從「通靈系統」到穩健架構的契機3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言